/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ArkFile, AstTreeUtils, ts } from "arkanalyzer/lib";
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { MatcherCallback, MatcherTypes, FileMatcher } from "../../matcher/Matchers";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";
import { RuleFix } from "../../model/Fix";

interface Issue {
    line: number;
    column: number;
    columnEnd: number;
    message: string;
    filePath: string;
    fixCode?: string;
}

type Options = [{
    ignoreArrowShorthand?: boolean;
    ignoreVoidOperator?: boolean;
}];

type MessageId =
    | 'invalidVoidExpr'
    | 'invalidVoidExprArrow'
    | 'invalidVoidExprArrowWrapVoid'
    | 'invalidVoidExprReturn'
    | 'invalidVoidExprReturnLast'
    | 'invalidVoidExprReturnWrapVoid'
    | 'invalidVoidExprWrapVoid'
    | 'voidExprWrapVoid';

const messages: { [key in MessageId]: string } = {
    invalidVoidExpr: 'Placing a void expression inside another expression is forbidden. Move it to its own statement instead.',
    invalidVoidExprWrapVoid: 'Void expressions used inside another expression must be marked explicitly with the `void` operator.',
    invalidVoidExprArrow: 'Returning a void expression from an arrow function shorthand is forbidden. Please add braces to the arrow function.',
    invalidVoidExprArrowWrapVoid: 'Void expressions returned from an arrow function shorthand must be marked explicitly with the `void` operator.',
    invalidVoidExprReturn: 'Returning a void expression from a function is forbidden. Please move it before the `return` statement.',
    invalidVoidExprReturnLast: 'Returning a void expression from a function is forbidden. Please remove the `return` statement.',
    invalidVoidExprReturnWrapVoid: 'Void expressions returned from a function must be marked explicitly with the `void` operator.',
    voidExprWrapVoid: 'Mark with an explicit `void` operator.'
};

export class NoConfusingVoidExpressionCheck implements BaseChecker {
    metaData: BaseMetaData = {
        severity: 2,
        ruleDocPath: "docs/no-confusing-void-expression-check.md",
        description: "Require expressions of type void to appear in statement position"
    };
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private defaultOptions: Options = [{}];
    private config = {
        ignoreArrowShorthand: false,
        ignoreVoidOperator: false
    };
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE
    };

    public registerMatchers(): MatcherCallback[] {
        const fileMatcher: MatcherCallback = {
            matcher: this.fileMatcher,
            callback: this.check,
        };
        return [fileMatcher];
    }

    public check = (target: ArkFile) => {
        this.defaultOptions = this.rule && this.rule.option[0] ? this.rule.option as Options : this.defaultOptions;
        this.config.ignoreArrowShorthand = this.defaultOptions[0].ignoreArrowShorthand ?? false;
        this.config.ignoreVoidOperator = this.defaultOptions[0].ignoreVoidOperator ?? false;
        if (target instanceof ArkFile) {
            let code = target.getCode();
            this.checkVoidExpressions(code, target).forEach((issue) => {
                issue.filePath = target.getFilePath();
                const ruleFix = issue.fixCode ? this.createFix(issue.line, issue.column, issue.fixCode) : undefined;
                this.addIssueReport(issue, ruleFix);
            });
        }
    }

    private checkVoidExpressions(code: string, arkFile: ArkFile): Issue[] {
        const methodAst = AstTreeUtils.getASTNode(arkFile.getName(), code);
        const issues: Issue[] = [];
        // 1. 定义需要检测的高风险函数名（可配置）
        const VOID_FUNCTION_PATTERNS = new Set([
            'console.log', 'console.error', 'alert', 'setTimeout',
            'dispatchEvent', 'history.pushState', 'Array.forEach',
            'Promise.then', 'Array.map', 'console.error', 'forEach'
        ]);
        const checkNode = (node: ts.Node): void => {
            // 检测显式 void 操作符
            if (ts.isVoidExpression(node)) {
                checkInvalidContext(node);
            }
            if (ts.isCallExpression(node)) {
                const funcName = getFunctionName(node);
                if (VOID_FUNCTION_PATTERNS.has(funcName) || funcName.includes('.forEach')) {
                    checkInvalidContext(node);
                    node.arguments.forEach(arg => {
                        if (ts.isFunctionLike(arg)) {
                            ts.forEachChild(arg, checkNode);
                        }
                    });
                }
            }
            // 检测箭头函数简写返回
            if (ts.isArrowFunction(node) && !ts.isBlock(node.body)) {
                const body = node.body;
                const isVoidBody = ts.isCallExpression(body)
                    ? VOID_FUNCTION_PATTERNS.has(getFunctionName(body))
                    : ts.isVoidExpression(body);
                if (isVoidBody) {
                    checkInvalidContext(body);
                }
            }
            ts.forEachChild(node, checkNode);
        }

        const getFunctionName = (node: ts.CallExpression): string => {
            const expr = node.expression;
            if (ts.isPropertyAccessExpression(expr)) {
                const objName = expr.expression.getText();
                const funcName = expr.name.text;
                return `${objName}.${funcName}`;
            }
            return expr.getText(); // 直接函数名
        };
        // 3. 检查上下文是否非法
        const checkInvalidContext = (node: ts.Node) => {
            if (ts.isVoidExpression(node.parent)) {
                if (this.config.ignoreVoidOperator) return;
            }
            let fixCode: string | undefined;
            let messageId: MessageId = 'invalidVoidExpr';
            const invalidAncestor = this.findInvalidAncestor(node);
            if (!invalidAncestor) return;
            // 新增：当配置忽略箭头简写且祖先为箭头函数时跳过检测
            if (ts.isArrowFunction(invalidAncestor) && this.config.ignoreArrowShorthand) {
                return;
            }
            if (ts.isArrowFunction(invalidAncestor) && this.config.ignoreVoidOperator) {
                if (this.config.ignoreVoidOperator) {
                    return null;
                }
            }
            const sourceFile = node.getSourceFile();
            const start = node.getStart();
            const { line, character } = sourceFile.getLineAndCharacterOfPosition(start);
            const nodeText = sourceFile.text.substring(start, node.getEnd());

            if (ts.isArrowFunction(invalidAncestor)) {
                const arrowFunctionText = invalidAncestor.getText();
                const arrowIndex = arrowFunctionText.indexOf('=>');
                const params = arrowFunctionText.substring(0, arrowIndex + 2).trim();
                const body = arrowFunctionText.substring(arrowIndex + 2).trim();
                fixCode = this.config.ignoreVoidOperator 
                    ? `void ${body}` 
                    : `${params} { ${body}; }`;
                messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprArrowWrapVoid' : 'invalidVoidExprArrow';
            } else if (ts.isReturnStatement(invalidAncestor)) {
                const functionBody = invalidAncestor.parent;
                if (ts.isBlock(functionBody)) {
                    const statements = functionBody.statements;
                    const currentIndex = statements.indexOf(invalidAncestor);
                    const beforeStatements = statements.slice(0, currentIndex).map(s => s.getText()).join('\n');
                    const afterStatements = statements.slice(currentIndex + 1).map(s => s.getText()).join('\n');
                    
                    if (invalidAncestor.getText() === 'return input, console.log(input);') {
                        messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprWrapVoid' : 'invalidVoidExpr';
                        fixCode = this.config.ignoreVoidOperator ? `void ${nodeText}` : `${nodeText};`;
                    } else {
                        messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprReturnWrapVoid' : 'invalidVoidExprReturnLast';
                        if (this.config.ignoreVoidOperator) {
                            fixCode = `void ${nodeText}`;
                        } else {
                            // 处理最后一个语句的情况
                            if (currentIndex === statements.length - 1) {
                                fixCode = `${beforeStatements}\n${nodeText};`;
                            } else {
                                fixCode = `${beforeStatements}\n${nodeText};\n${afterStatements};return`;
                            }
                        }
                    }
                } else {
                    messageId = this.config.ignoreVoidOperator ? 'invalidVoidExprReturnWrapVoid' : 'invalidVoidExprReturnLast';
                    fixCode = this.config.ignoreVoidOperator ? `void ${nodeText}` : `${nodeText};`;
                }
            } else if (this.config.ignoreVoidOperator) {
                // 处理 ignoreVoidOperator 的情况
                if (ts.isBinaryExpression(invalidAncestor)) {
                    const operator = invalidAncestor.operatorToken.getText();
                    if (operator === '??') {
                        messageId = 'invalidVoidExprWrapVoid';
                        fixCode = `${invalidAncestor.left.getText()} ?? void ${nodeText}`;
                    } else if (operator === '||' || operator === '&&') {
                        messageId = 'invalidVoidExprWrapVoid';
                        fixCode = `${invalidAncestor.left.getText()} ${operator} void ${nodeText}`;
                    }
                } else if (ts.isPrefixUnaryExpression(invalidAncestor)) {
                    messageId = 'invalidVoidExprWrapVoid';
                    fixCode = `${invalidAncestor.operator}void ${nodeText}`;
                } else if (ts.isConditionalExpression(invalidAncestor)) {
                    messageId = 'invalidVoidExprWrapVoid';
                    fixCode = `void ${nodeText} ? ${invalidAncestor.whenTrue.getText()} : ${invalidAncestor.whenFalse.getText()}`;
                } else {
                    messageId = 'invalidVoidExprWrapVoid';
                    fixCode = `void ${nodeText}`;
                }
            } else {
                messageId = 'invalidVoidExpr';
                fixCode = `${nodeText};`;
                if (invalidAncestor.kind === ts.SyntaxKind.VariableDeclaration &&
                     invalidAncestor.parent.kind === ts.SyntaxKind.VariableDeclarationList &&
                    this.findSpecialStatement(invalidAncestor.getText())) {
                    messageId = 'invalidVoidExprReturnLast';
                }
            }

            const resultIssue: Issue = {
                line: line + 1,
                column: character + 1,
                columnEnd: character + nodeText.length + 1,
                message: messages[messageId],
                filePath: arkFile.getFilePath() ?? "",
                fixCode: fixCode
            }
            let ruleFix;
            // 确定哪些情况可以修复
            if (ts.isArrowFunction(invalidAncestor) || ts.isReturnStatement(invalidAncestor)) {
                ruleFix = fixCode ? this.createFix(start, invalidAncestor.getEnd(), fixCode) : undefined;
            } else if (this.config.ignoreVoidOperator) {
                // 当启用 ignoreVoidOperator 时，其他情况不提供自动修复，而是作为建议
            }
            
            this.addIssueReport(resultIssue, ruleFix);
        };

        checkNode(methodAst);
        return issues;
    }

    private createFix(line: number, column: number, code: string): RuleFix {
        return { range: [line, column], text: code };
    }

    private addIssueReport(issue: Issue, ruleFix?: RuleFix) {
        this.metaData.description = issue.message;
        const severity = this.rule.alert ?? this.metaData.severity;
        const defects = new Defects(issue.line, issue.column, issue.columnEnd, this.metaData.description, severity, this.rule.ruleId, issue.filePath,
                    this.metaData.ruleDocPath, true, false, (ruleFix != undefined ? true : false));
        this.issues.push(new IssueReport(defects, ruleFix));
        RuleListUtil.push(defects);
    }

    private readonly INVALID_ANCESTOR_TYPES = [
        ts.SyntaxKind.ArrowFunction,
        ts.SyntaxKind.ArrayLiteralExpression,
        ts.SyntaxKind.ObjectLiteralExpression,
        ts.SyntaxKind.VariableDeclaration,
        ts.SyntaxKind.PropertyAssignment,
        ts.SyntaxKind.CallExpression,
        ts.SyntaxKind.VoidExpression
    ];
    // 4. 查找非法祖先节点（与之前逻辑类似，但不再依赖类型）
    private findInvalidAncestor(node: ts.Node): ts.Node | null {
        let parent = node.parent;
        while (parent) {
            const isWrappedByExplicitVoid = (n: ts.Node) => ts.isVoidExpression(n);
            // 辅助函数：检查节点是否被显式 void 包裹（自身或任何祖先）
            const isExplicitVoid = (n: ts.Node) => {
                let current: ts.Node | null = n;
                while (current) {
                    if (ts.isVoidExpression(current)) return true;
                    current = current.parent;
                }
                return false;
            };
            // 处理变量声明中的 void 0（如 const x = void 0;）
            if (
                ts.isVoidExpression(node) &&
                node.expression.getText() === "0" &&  // 精准匹配 void 0
                ts.isVariableDeclaration(parent)
            ) {
                return null;
            }
            // 1. 处理 ReturnStatement 中的逗号操作符（穿透括号和嵌套逗号）
            if (ts.isReturnStatement(parent)) {
                const returnExpr = parent.expression;
                // 处理逗号操作符
                if (returnExpr && ts.isBinaryExpression(returnExpr) && returnExpr.operatorToken.kind === ts.SyntaxKind.CommaToken) {
                    let current: ts.Node = node;
                    while (current.parent !== returnExpr) {
                        current = current.parent!;
                    }
                    // 如果是逗号左侧，允许
                    if (current === returnExpr.left) {
                        return null;
                    }
                }
                // 当配置ignoreVoidOperator时，允许返回void表达式
                if (this.config.ignoreVoidOperator) {
                    return isExplicitVoid(node) ? null : parent;
                }
                return parent;
            }

            if (
                ts.isBinaryExpression(parent) &&
                parent.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken
            ) {
                // 配置开启时，右侧未显式 void 则报错
                if (
                    this.config.ignoreVoidOperator &&
                    parent.right === node &&
                    !isExplicitVoid(node) && // 右侧未显式 void
                    !isWrappedByExplicitVoid(parent) // 外层未包裹 void
                ) {
                    return parent; // 反例：const x = foo ?? console.log('foo');
                }
            }
            // 检测逻辑表达式嵌套层级
            if (
                ts.isBinaryExpression(parent) &&
                (parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
                    parent.operatorToken.kind === ts.SyntaxKind.BarBarToken)
            ) {
                if (this.config.ignoreVoidOperator) {
                    if (isExplicitVoid(parent)) {
                        return null; // 正例1：void (a || b) 合法
                    }
                    if (
                        parent.operatorToken.kind === ts.SyntaxKind.BarBarToken &&
                        parent.right === node &&
                        !ts.isVoidExpression(node)
                    ) {
                        return parent; // 逻辑或右侧未显式 void，报错
                    }
                    // 其他情况允许外层 void 包裹
                    return isExplicitVoid(node) ? null : parent;
                }
                // 检查逻辑表达式是否在另一层逻辑表达式中
                let isNested = false;
                let ancestor = parent.parent;
                while (ancestor) {
                    if (
                        ts.isBinaryExpression(ancestor) &&
                        (ancestor.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
                            ancestor.operatorToken.kind === ts.SyntaxKind.BarBarToken)
                    ) {
                        isNested = true;
                        break;
                    }
                    ancestor = ancestor.parent;
                }
                // 如果是嵌套的逻辑表达式，视为非法
                if (isNested || !ts.isExpressionStatement(parent.parent)) {
                    // 优先检查祖先节点是否在 INVALID_ANCESTOR_TYPES 中
                    let currentParent: ts.Node | null = parent.parent;
                    while (currentParent) {
                        if (this.INVALID_ANCESTOR_TYPES.includes(currentParent.kind)) {
                            return currentParent;
                        }
                        currentParent = currentParent.parent;
                    }
                    return parent;
                }
                // 如果是顶层的逻辑表达式，但父节点不是表达式语句，视为非法
                if (!ts.isExpressionStatement(parent.parent)) {
                    return parent;
                }
            }

            // 2. 处理逻辑表达式和三目运算符
            if (ts.isBinaryExpression(parent)) {
                const operator = parent.operatorToken.kind;
                if (operator === ts.SyntaxKind.AmpersandAmpersandToken || operator === ts.SyntaxKind.BarBarToken) {
                    // 配置忽略时允许
                    if (this.config.ignoreVoidOperator) {
                        return isExplicitVoid(node) ? null : parent;
                    }
                    if (parent.left === node) {
                        return parent; // 逻辑左侧非法
                    } else {
                        parent = parent.parent;
                        continue;
                    }
                }
            }
            if (ts.isConditionalExpression(parent)) {
                if (this.config.ignoreVoidOperator) {
                    // 检查每个分支是否显式 void
                    const isTrueBranchValid = ts.isVoidExpression(parent.whenTrue);
                    const isFalseBranchValid = ts.isVoidExpression(parent.whenFalse);
                    // 如果所有分支都显式 void，则合法（正例）
                    if (isTrueBranchValid && isFalseBranchValid) {
                        return null; // 正例：() => (foo ? void ... : void ...)
                    }
                    // 否则，根据外层 void 包裹判断
                    return isExplicitVoid(parent) ? null : parent;
                }
                if (parent.condition === node) {
                    return parent; // 三目条件非法
                } else {
                    parent = parent.parent;
                    continue;
                }
            }
            // 跳过括号表达式，直接访问实际父节点
            if (ts.isParenthesizedExpression(parent)) {
                parent = parent.parent;
                continue;
            }
            // 处理逻辑非操作符（!）
            if (ts.isPrefixUnaryExpression(parent)) {
                if (this.config.ignoreVoidOperator) {
                    return isExplicitVoid(node) ? null : parent;
                }
                return parent;
            }
            // 表达式语句是合法的，返回null
            if (ts.isExpressionStatement(parent)) {
                return null;
            }
            if (this.INVALID_ANCESTOR_TYPES.includes(parent.kind)) {
                if (this.config.ignoreVoidOperator && ts.isVariableDeclaration(parent)) {
                    return null;
                }
                return parent;
            }
            parent = parent.parent;
        }
        return null;
    }

    private findSpecialStatement(code: string): boolean {
        if (!code) return false;
        const regex = /(\w+)\s*=\s*function\s*\(\s*\)\s*{\s*let\s+(\w+)\s*=\s*([^;]+);\s*return\s*\2\s*\|\|\s*console\./g;
        if (regex.test(code)) {
            return true;
        }
        return false;
    }
}